//////////////////////////////////////////////////////////////////////////////
//
// File:  conGuy.cc
//
// Description:  This is the implementation file for the conGuy class.
//   This class manages and processes all the necessary actions
//   required to connect to bnet.  (accnt, pwd and auth challenges).
//   It also handles all the interface and packet communication to bnet
//   after the connect.
// 
// Status:  Complete
//
// Notes:  
//
//   1.  I have "exeVer = 0x01010101; " hardcoded because I haven't
//   looked into wine to find the translated linux equivalent of the
//   windows API that extracts dll version info.  (search for 'exeVer').
//   
//   UPDATE: After rigoriously searching through the pages and pages of
//   wine code, I found out that wine does not have the code to extract
//   the exe version.
//
//   Note, the way it's stored here is in reverse order of how it's
//   logged in the packet logs. (due to endianess)
// 
//   2.  I have u32 'versionByte' hardcoded in function
//   bool conGuy::s_pkt_SID_AUTH_INFO().  This has been noted to
//   change less often from version to version.  It probably won't
//   anymore, but if it does, that's where it is located.
//
//   3.  As a reference to anyone trying to figure out how to make
//   this work in the future, here's the information on the login
//   packet sequence.  You will need to get a packet logger (such as
//   ethereal) and log the packets from a real Starcraft Battle.net
//   client.  Then look at the packet logs.  This is the login
//   sequence: (In parentheses is the packet ID in the form 0xHH).
//
// SEND -> Protocol byte(01)
// SEND ->SID_AUTH_INFO(0x50)
// RECV <-SID_PING(0x25)
// RECV <-SID_AUTH_INFO(0x50)
// SEND ->SID_PING(0x25) [Optional]
// SEND ->SID_AUTH_CHECK(0x51)
// RECV <-SID_AUTH_CHECK(0x51)
// SEND ->SID_LOGONRESPONSE(0x29)
// RECV <-SID_LOGONRESPONSE(0x29)
// SEND ->SID_UDPPINGRESPONSE(0x14) [SEXP/STAR/W2BN]
// SEND ->SID_ENTERCHAT(0x0A)
//
// That being said, here's the info on the packet headers and how to
// tell the difference between different packets.  Each packet start
// like this:
//
// (BYTE)      Always 0xFF
// (BYTE)      Message ID
// (WORD)      Message length, including this header (2 bytes)
// (VOID)      Message Data
// 
// BYTE  == 0xHH
// WORD  == 0xHH 0xHH
// DWORD == 0xHH 0xHH 0xHH 0xHH
//
///////////////////////////////////////////////////////////////
//
// exeVer is located in (SEND->SID_AUTH_CHECK(0x51)), and it's the
// 9th-12th bytes.  ie. if the packet looks like this: 
//
// 0xHH 0xHH 0xHH 0xHH 0xHH 0xHH 0xHH 0xHH 0x** 0x** 0x** 0x** 
//                                         ^ 
//                                         ^ 
//                                         ^ is where the exeVer is.
//
// 1 byte == 0xHH
//
// Also, note that the way it's stored here is in reverse order of how
// it's logged in the packet logs. (due to endianess).  So you need to
// take every two hex values and flip them.  For example:
//
// 0x12 0x34 0x45 0x67  
// in the packet log becomes -->  0x67 0x45 0x34 0x12
//
//////////////////////////////////////////////////////////////
//
// u32 versionByte is located in the SEND->SID_AUTH_INFO(0x50) packet.
// It is the 5th dword, or the 18-21st bytes.
//
/////////////////////////////////////////////////////////////////////
//
#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
#include <assert.h>

using namespace std;

// My stuff:
#include "conGuy.h"
#include "buff.h"
#include "../parser/parser.h"
#include "types.h"
#include "../event/event.h"


// My #define's and global externs:

// conGuys' extern ofstream *Debug shouldn't clobber each other 
// since most of the output to these ofstreams are at the connect, 
// done asynchronously.  If it becomes a problem
// I can change these to be not externed and be pointing to another
// file ofstream opened at the constructor.
extern QTextStream *Debug;
//extern ofstream *gitmlog;

// This is the default maximum recv message size.
#define MAX_MSG 1024  

// Default connect config file
#define CONGUY_CONFIG_FILE "config/connect.cfg"  

// This is the number of seconds between the
// last send time and the time a SID_NULL packet will need to be sent.
#define SECONDS_SID_NULL 480 



//////////////////////////////////////////////////////////////////////////////
//
// Function:  conGuy::conGuy() 
//
// Description:
//   This is the conGuy default constructor.  Initializes default values
//   and inits the pthread mutexes.
//
conGuy::conGuy( QObject* parent, const char* name, bool isBnetConGuy ) 
    : 
    QObject( parent, name ),
    bnetConGuy( isBnetConGuy )
{
  port = -1;
  done = false;

  // Initialize time as 0;  (This is for the flood protection)
  lastSendTime.setTime_t( 0 );
  lastSendSize = 0;

  // Set socket as blocking.
  qtsocket.setBlocking( true );

  // Set pointer to NULL.
  dns = 0;

  // Turn off flood control untill we're logged in.
  enableFloodControl( false );

  // Initialize start time to zero untill we actually connect.
  startTime.setTime_t( 0 );
} // End of    conGuy::conGuy()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  conGuy::~conGuy()
//
// Description:  
//   This is the conGuy destructor.  It disconnects the socket if it is open
//   and destroys all mutexes.
//
conGuy::~conGuy() {
    disconnectNow();
}


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::connectNow(string this_gateway)
//
// Description:  
//   Read the config file in and do the essential stuff to connect to bnet.
// 
//
bool conGuy::connectNow(string this_gateway) {
  gateway = this_gateway;
  done = false;

  //   // TODO:  Should this be commented out?  or not?
  //   if(isConnected() == true)
  //       disconnectNow();
  
  string logfilename = string(LOGFILE_BASE) + 
                       string("connection.log.") + 
                       this_gateway;
  time_t rawtime;
  struct tm * timeinfo;
  time ( &rawtime );
  timeinfo = localtime ( &rawtime );

  // Close the conlog if it is open right now.
  if(conlog.is_open()) {
    conlog.close();
  }

  // Open the logfile for appending.
  conlog.open(logfilename.c_str(),
	       ios_base::out | ios_base::app);

  // Log timestamp of connection start.
  conlog << endl
	  << "---------------------------------------------------------------"
	  << endl << "GiTMbot connecting at:                 "
	  << string(asctime(timeinfo))
	  << "---------------------------------------------------------------"
	  << endl;
  
  conlog << "Reading in connection configuration file " 
       << string(CONGUY_CONFIG_FILE)
       << " ..." << endl;

  if(!read_config(this_gateway)) {
    *Debug << "ERROR in read_config call" << endl;
    return false;
  }


  conlog << "Doing DNS lookup on " << server << " ..." << endl;

  if( dns != 0 )
  {
      delete dns;
      dns = 0;
  }
  dns = new QDns( server );
  connect( dns, SIGNAL(resultsReady()),
           this, SLOT(connectToBnet()) );

  if( !dns->isWorking() )
  {
      conlog << "DNS lookup isn't working.";
      return false;
  }

  // Turn on flood control.
  enableFloodControl( true );

  return true;
}  // end of bool conGuy::connectNow()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::disconnectNow()
//
// Description: 
//   This function closes the socket if it is open.
//
bool conGuy::disconnectNow() {
  *Debug << "DEBUG:  disconnectNow()" << endl;
  conlog << "Disconnecting from the server." << endl;

  if( dns != 0 )
  {
      delete dns;
      dns = 0;
  }

  qtsocket.close();
  // Reset the start time to 0.
  startTime.setTime_t( 0 );


  time_t rawtime;
  struct tm * timeinfo;
  time ( &rawtime );
  timeinfo = localtime ( &rawtime );

  conlog << "GiTMbot disconnect at:                 "
	  << string(asctime(timeinfo))
	  << "---------------------------------------------------------------"
	  << endl;

  conlog.close();


  // Turn off flood control.  (We'll turn it back on when we're connected).
  enableFloodControl( false );

  return true;
}  // end of    bool conGuy::disconnectNow()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::isConnected()
//
// Description:
//   This function returns if the socket is valid or not.  (connected).
//
bool conGuy::isConnected() {
    if( qtsocket.isValid() == true &&
        qtsocket.error() == QSocketDevice::NoError )
    {
        return true;
    }
    else
    {
        return false;
    }
}

//////////////////////////////////////////////////////////////////////////////
//
// Function:  string conGuy::getMainGateway()
//
// Description: 
//   This returns the gateway that the main bot will run on.  (The one
//   with the sql database).
//
string conGuy::getMainGateway() {
  return bot_gateway;
}

//////////////////////////////////////////////////////////////////////////////
//
// Function:  string conGuy::getGateway()
//
// Description:
//   This function returns the gateway that the conGuy is connected to.
//
string conGuy::getGateway() {
  return gateway;
}

//////////////////////////////////////////////////////////////////////////////
//
// Function:  sendQueue* conGuy::getSendQueue()
//
// Description:
//   Return the pointer to the toSend sendQueue.  This 'sendQueue'  is a queue
//   of messages that need to be sent out.  Remember!  The sendQueue
//   has a pthread_mutex!  Lock before modifying.  :)
//
sendQueue* conGuy::getSendQueue() {
  return &toSend;
}

//////////////////////////////////////////////////////////////////////////////
//
// Function:  sendRequests* conGuy::getSendRequests()
//
// Description:
//   Return the pointer to the requests sendRequests.  Remember!  The 
//   sendRequests object has a pthread_mutex!  Lock before modifying.
//
sendRequests* conGuy::getSendRequests() {
  return &requests;
}

//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::purgeQueues(); 
//
// Description:
//   This function purges the sendQueue and the sendRequests.
//
//   If there is any sends in our sendQueue we want to pop them all off.
//   If there are any pending requests from other conGuys, we 
//   should at least alert to the requesting conGuy (if he's still connected)
//   that we are disconnected.

bool conGuy::purgeQueues() {
    //  int rc;

  // Check if they are already purged.
  if(toSend.out.size() == 0 && requests.out.size() == 0)
    return true;

  // Purge our sendQueue.
  toSend.mutex.lock();
  while(toSend.out.size() > 0)
    toSend.out.pop_front();
  toSend.mutex.unlock();  

  // Purge our sendRequests.
  requests.mutex.lock();
  while(requests.out.size() > 0) {
    string msg         = requests.out.front();
    conGuy *comingFrom = requests.fromConGuy.front();
    string fromUser    = requests.fromWho.front();
    requests.out.pop_front();
    requests.fromConGuy.pop_front();
    requests.fromWho.pop_front();

    if(comingFrom->isConnected() == true) {
      // Return a message that says the bot is disconnected.
      sendQueue *toSendTheirs = comingFrom->getSendQueue();
      (toSendTheirs->mutex).lock();
      (toSendTheirs->out).push_back( string("/w ") + fromUser + string(" ") +
				   getGateway() + 
				   string(" bot is disconnected."));
      (toSendTheirs->mutex).unlock();
    }
    else {
      // So the guy we were servicing a request for isn't connected.  Oh well.
      // I suppose we'll continue on since we've popped already...
    }
  }
  requests.mutex.unlock();
  // All done purging!
  return true;
}



//////////////////////////////////////////////////////////////////////////////
//
// Function:  string conGuy::homeChannel()
//
// Description:
//   Returns the home_channel designated in the config file.  
//
string conGuy::homeChannel() {
  return home_channel;
}


//////////////////////////////////////////////////////////////////////////////
//
// Function:  string conGuy::username()
//
// Description:
//   Returns the unique username designated at login time.  
//
string conGuy::username() {
  return unique_username;
}


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::getHaveOps()
// 
// Description:
//   Accessor function that lets us know if we have channel ops or not.
//
bool conGuy::getHaveOps() {
  return haveOps;
}


//////////////////////////////////////////////////////////////////////////////
// 
// Function:  bool conGuy::setHaveOps(bool ops)
//
// Description:
//   Sets the haveOps flag.
//
bool conGuy::setHaveOps(bool ops) {
  haveOps = ops;
  return true;
}

//////////////////////////////////////////////////////////////////////////////
//
// Function:  time_t conGuy::getStartTime() 
//
// Description:
//   Accessor function that gives us the time_t startTime that it takes the 
//   timestamp of when it connects.
//
QDateTime conGuy::getStartTime() {
  return startTime;
}


void conGuy::setBnetConGuy( const bool& value )
{ 
    bnetConGuy = value;
}

bool conGuy::isBnetConGuy() const
{
    return bnetConGuy;
}


void conGuy::run()
{
    unsigned int count = 0;
  while( true )
  {
      if( this == 0 )
      {
          // This should never happen.
          conlog << "DEBUG:  Ahhh, '" << gateway << "' conGuy is null!, "
              << "count == " << count << endl;
      }


    // TODO:  fix this function
    Event someNewEvent = this->gimmeEvent();
    emit newEvent( this, someNewEvent );
    //    new_event = myConGuy->gimmeEvent();
  }
}


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::say(string msg)
//
// Description:
//   Send bnet a chat message or command.
// 
bool conGuy::say(string msg) {
  return s_pkt_SID_CHATCOMMAND(msg);
}


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::read_config(string this_gateway)
//
// Description:
//   This function reads the #defined CONGUY_CONFIG file for the configuration
//   stuff.
//
bool conGuy::read_config(string this_gateway) {

  // Parse dat shizznitz!
  parser parserGuy(string(CONGUY_CONFIG_FILE));

  string hash1;   // hash file filenames
  string hash2;
  string hash3;
  string password;
  string cdkey;
  // Fetch them bones.

  // Fetch the gateway that the ACTUAL bot will run on.  (not necessarily this
  //   conGuy).
  if(!parserGuy.fetch(string("gateway"), bot_gateway)) {
    *Debug << "ERROR: Couldn't get gateway from parser.  (check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get gateway from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  *Debug << "DEBUG:  Read in from config file gateway: " << bot_gateway << endl;
  
  
  // Fetch server.
  if(!parserGuy.fetch( string("server_") + this_gateway, server)) {
    *Debug << "ERROR: Couldn't get server from parser.  (check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get server from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  *Debug << "DEBUG:  Read in from config file server: " << server << endl;

  // Fetch port #.
  string portStr;
  if(!parserGuy.fetch(string("port"), portStr)) {
    *Debug << "ERROR: Couldn't get port # from parser.  (check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get port # from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  // Convert from string to int.
  port = atoi(portStr.c_str());
  
  // Fetch home channel.
  if(!parserGuy.fetch(string("home_channel_") + this_gateway, home_channel)) {
    *Debug << "ERROR: Couldn't get home_channel from parser.(check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get home_channel from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }

  // Fetch user accnt name.
  if(!parserGuy.fetch(string("user_") + this_gateway, account)) {
    *Debug << "ERROR: Couldn't get user accnt from parser.(check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get user account from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  
  // Fetch password.
  if(!parserGuy.fetch(string("password_") + this_gateway, password)) {
    *Debug << "ERROR: Couldn't get password from parser.(check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get password from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  
  // Fetch cdkey.
  if(!parserGuy.fetch(string("cdkey"), cdkey)) {
    *Debug << "ERROR: Couldn't get cdkey from parser.(check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get cdkey from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }

  // Fetch hash1
  if(!parserGuy.fetch(string("hash1"), hash1)) {
    *Debug << "ERROR: Couldn't get hash1 from parser.(check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get hash1 filename from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  // Fetch hash2
  if(!parserGuy.fetch(string("hash2"), hash2)) {
    *Debug << "ERROR: Couldn't get hash2 from parser.(check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get hash2 filename from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  // Fetch hash3
  if(!parserGuy.fetch(string("hash3"), hash3)) {
    *Debug << "ERROR: Couldn't get hash3 from parser.(check cfg file?)"
	   << endl << "         " << __FILE__ << "(" << __LINE__ << ")" 
	   << endl;
    conlog << "ERROR: Couldn't get hash3 filename from config file."
	     << "  (check connect.cfg file?)" << endl;
    return false;
  }
  
  // Tell authGuy what those values are.
  auth.setAccount(account);
  auth.setPassword(password);
  auth.setCdkey(cdkey);
  auth.setHashfile1(hash1);
  auth.setHashfile2(hash2);
  auth.setHashfile3(hash3);
  
  *Debug << "DEBUG:  account:  " << account << endl;
  *Debug << "DEBUG:  password: " << password << endl;
  *Debug << "DEBUG:  cdkey:    " << cdkey << endl;
  *Debug << "DEBUG:  hash1:    " << hash1 << endl;
  *Debug << "DEBUG:  hash2:    " << hash2 << endl;
  *Debug << "DEBUG:  hash3:    " << hash3 << endl;
  
  *Debug << "DEBUG: Finished extracting data needed from conGuy config"<< endl;
  
  return true;
} // end of bool conGuy::read_config()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  int conGuy::readit(void*msg_data, 
//                               u32 msg_len)
//
// Description:  
//   Reads data from a socket.  msg_len is max message size.  The return value
//   is how many bytes were read.
//
//
//
int conGuy::readit(void*msg_data, u32 msg_len) {
  Q_LONG rc = qtsocket.readBlock( static_cast<char*>(msg_data), msg_len );
  
  if( rc < 0 )
  {
      conlog << string("ERROR:  Socket read error.  ")
             << printErrorType( qtsocket.error() )
             << endl;
      return -1;
  }
  else if( rc == 0 )
  {
      conlog << string("ERROR:  Socket read error.  ")
             << string("Remote host closed the connection.")
             << endl;
      return -1;
  }
  else 
  {
      return rc;
  }
} // end of int conGuy::readit()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  int conGuy::writeit(void*msg_data, 
//                                u32 msg_len)
// 
// Description:
//   Writes data to a socket descriptor or length msg_len.
//
bool conGuy::writeit(void*msg_data, u32 msg_len) {
  char *buf_ptr;
  Q_LONG sent = 0;

  buf_ptr = (char*)msg_data;
  /* Handle possible interrupt before entire buffer
   * is written out.
   */
  sent = 0;
  while( msg_len > 0 && sent != -1) {

      sent = qtsocket.writeBlock( buf_ptr, msg_len );

    /* Here we handle the possibility of writing less
     * than the entire buffer due to an interrupt.
     */
    if( sent > 0 ) {
      msg_len -= sent;
      buf_ptr += sent;
    }
    else if (sent < 0)  {
        conlog << string("ERROR:  Socket write error.  ") 
               << printErrorType( qtsocket.error() )
               << endl;
      return false;
    }
  }

  // Should have sent everything.
  Q_ASSERT( msg_len == 0 );

  return true;
} // end of int conGuy::writeit()

//////////////////////////////////////////////////////////////////////////////
//
// Function:  int conGuy::writeit(void*msg_data, 
//                                u32 msg_len)
// 
// Description:
//   Writes data to a socket descriptor or length msg_len.
//
void conGuy::enableFloodControl( const bool& enable )
{
    if( enable == true )
    {
        // Enable flood control.
        sleep_sec_req = 0;         // Minimum required seconds to sleep
        sleep_msec_req = 1200;     // Minimum required milliseconds to sleep.
        sleep_msec_per_byte = 42;  // Number of milliseconds to sleep per byte
                                   // when packet size is > 30 bytes.
    }
    else
    {
        // Disable flood control.
        sleep_sec_req = 0;        // Minimum required seconds to sleep
        sleep_msec_req = 0;       // Minimum required milliseconds to sleep.
        sleep_msec_per_byte = 0;  // Number of milliseconds to sleep per byte
                                  //   when packet size is > 30 bytes.
    }
} // End of    void conGuy::enableFloodControl( const bool& enable )

//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::updateLastSendTime()
//
// Description:
//   Self explanatory I believe...
//
bool conGuy::updateLastSendTime() {
  lastSendTime = QDateTime::currentDateTime();
  lastSendSize = sendBuff.size();
  return true;
} // end of bool conGuy::updateLastSendTime()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::sleepRequiredTime()
//
// Description:
//   Call this before every send.  (sendBuffOut)
//   It does a sleep() or usleep() as required.
//
bool conGuy::sleepRequiredTime() {
  // Flag to check if I need to sleep.
  bool needToSleep = false;
  
  // Current timestamp.
  QDateTime currTime = QDateTime::currentDateTime();

  // Time required to wait.
  long secReq  = sleep_sec_req;
  long msecReq = sleep_msec_req;

  // Time I will sleep:
  long secSleep;
  long msecSleep;

  // Elapsed time.
  long secElapsed;
  long msecElapsed;
  
  // If the packet size was >= 30 bytes, we use 35 milliseconds per byte.
  if(lastSendSize >= 30) {
    secReq  = (lastSendSize * sleep_msec_per_byte) / 1000;
    msecReq = (lastSendSize * sleep_msec_per_byte) % 1000;
  }
  
  // Set elapsed time.
  secElapsed  = currTime.toTime_t() - lastSendTime.toTime_t();
  msecElapsed = currTime.time().msec() - lastSendTime.time().msec();
  // We can't have negative times, can we?  Fix that.
  if(msecElapsed < 0) {
    secElapsed--;
    msecElapsed += 1000;
  }

  if(secElapsed < secReq) {
    needToSleep = true;
  }
  if(secElapsed == secReq && msecElapsed < msecReq) {
    needToSleep = true;
  }

  if(needToSleep == true) {
    secSleep  = secReq  - secElapsed;
    msecSleep = msecReq - msecElapsed;
    // We can't have negative times, can we?  Fix that.
    if(msecSleep < 0) {
      secSleep--;
      msecSleep += 1000;
    }
    if(secSleep  >= 1)
      this->sleep(secSleep);
    if(msecSleep >= 1)
      this->msleep(msecSleep);
  }
  return true;
}  // end of bool conGuy::sleepRequiredTime()



//////////////////////////////////////////////////////////////////////////////
//
// Function:   bool conGuy::connectToBnet()
//
// Description:
//   This function processes and does the essential sends & recvs necessary
//   to log on to bnet.
//
//   This is the logon sequence I use:
// 
// SEND -> 	Protocol byte	(01)
// SEND ->	SID_AUTH_INFO	(0x50)
// RECV <-	SID_PING	(0x25)
// RECV <-	SID_AUTH_INFO	(0x50)
// SEND ->	SID_PING	(0x25) [Optional], but I do it.
// SEND ->	SID_AUTH_CHECK	(0x51)
// RECV <-	SID_AUTH_CHECK	(0x51)
// SEND ->	SID_LOGONRESPONSE	(0x29)
// RECV <-	SID_LOGONRESPONSE	(0x29)
// SEND ->	SID_UDPPINGRESPONSE	(0x14) [Game Specific: SEXP/STAR/W2BN]
// SEND ->	SID_ENTERCHAT	(0x0A)
// SEND ->      SID_JOINCHANNEL (0x0C)
// RECV <-      SID_ENTERCHAT	(0x0A)
//
bool conGuy::connectToBnet() {
    disconnect( dns, SIGNAL( resultsReady() ), this, SLOT( connectToBnet() ) );
    QValueList<QHostAddress> addresses = dns->addresses();
    QHostAddress serverAddr = dns->addresses().first();

    conlog << string("Attempting to connect to ")
           << string( serverAddr.toString().ascii() )
           << string( " on port ")
           << QString::number( port ).ascii()
           << endl;

    if( ! qtsocket.connect( serverAddr, port ) )
    {
        conlog << string("ERROR:  Attempt to connect to server failed.  ")
               << printErrorType( qtsocket.error() ) 
               << endl;
        disconnectNow();
        return false;
    }
    else
    {
        conlog << "Successful connection established to server." 
               << endl;
    }
    
    //   int rc;
    //   u8 conGuyBuff[MAX_MSG];
  
    // Starting to send stuff for the log on process!!
    // See the top of this function for comments on the procedure I use.
  
    bool login_complete = false;

    conlog << "Proceeding to log into account..." << endl;

    // Send PROTOCOL BYTE.  (game protocol)
    if(!s_pkt_protocol()) {
        *Debug << "ERROR:    Send protocol byte failed.  " << endl
               << "          " << __FILE__ << "(" << __LINE__ << ")" << endl;
        conlog << "ERROR:  Send game protocol byte failed." << endl;
        return false;
    }
    *Debug << "DEBUG:  Just sent the protocol byte." << endl;

  
    // Send SID_AUTH_INFO.
    if(!s_pkt_SID_AUTH_INFO()) {
        *Debug << "ERROR:    Send packet SID_AUTH_INFO failed." << endl
               << "          " << __FILE__ << "(" << __LINE__ << ")" << endl;
        conlog << "ERROR:    Send packet SID_AUTH_INFO failed." << endl;
        return false;
    }
    *Debug << "DEBUG:  Just sent the SID_AUTH_INFO 0x50 packet" << endl;



    // Now we wait for responses to the stuff we've sent and react accordingly.
    recvBuff.clear();
    recvBuff.setStripped( false );
    while( !login_complete && qtsocket.isValid() ) {
        int timeout = 3000; // Milliseconds
        if( !readFullPacket( timeout ) )
        {
            conlog << string("Connect attempt failed.") << endl;
            disconnectNow();
            return false;
        }

        char pktID[16];
        sprintf(pktID, "%d", recvBuff.getPacketID());
        *Debug << (string("Processing input packet ") + string(pktID))
               << endl;
        switch(recvBuff.getPacketID()) {
      
        case SID_PING: {
            u32 pingVal;
            if(! r_pkt_SID_PING(pingVal)) {
                *Debug << (string("ERROR:  r_pkt_SID_PING() broke.")
                           + string("          ") + string( __FILE__ )
                           + string("(") 
                           + string( QString::number(int(__LINE__)).ascii() ) 
                           + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Recv a ping.") << endl;
            if(! s_pkt_SID_PING(pingVal)) {
                *Debug << (string("ERROR:  s_pkt_SID_PING() broke.")
                           + string("          ") + string( __FILE__ )
                           + string("(") 
                           + string( QString::number(int(__LINE__)).ascii() ) 
                           + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Sent a ping") << endl;
        } break;


        case SID_AUTH_INFO: {
            if(! r_pkt_SID_AUTH_INFO()) {
                *Debug << (string("ERROR:  r_pkt_SID_AUTH_INFO() broke.")
                           + string("          ") + string( __FILE__ )
                           + string("(") 
                           + string( QString::number(int(__LINE__)).ascii() ) 
                           + string(")"));
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << (string("Recv SID_AUTH_INFO"))
                   << endl;
            if(! s_pkt_SID_AUTH_CHECK()) {
                *Debug << (string("ERROR:  s_pkt_SID_AUTH_CHECK() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << (string("Sent SID_AUTH_CHECK")) << endl;
        } break;
    


        case SID_AUTH_CHECK: {
            if(! r_pkt_SID_AUTH_CHECK()) {
                *Debug << (string("ERROR:  r_pkt_SID_AUTH_CHECK() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            *Debug << (string("Recv SID_AUTH_CHECK")) << endl;
            if(! s_pkt_SID_LOGONRESPONSE()) {
                *Debug << (string("ERROR:  s_pkt_SID_LOGONRESPONSE() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << (string("Sent SID_LOGONRESPONSE")) << endl;
        } break;


        case SID_LOGONRESPONSE: {
            if(! r_pkt_SID_LOGONRESPONSE()) {
                *Debug << (string("ERROR:  r_pkt_SID_LOGONRESPONSE() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")")) 
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Recv SID_LOGONRESPONSE") << endl;

            if(! s_pkt_SID_UDPPINGRESPONSE()) {
                *Debug << (string("ERROR:  s_pkt_SID_UDPPINGRESPONSE() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Sent SID_UDPPINGRESPONSE") << endl;
      
            if(! s_pkt_SID_ENTERCHAT()) {
                *Debug << (string("ERROR:  s_pkt_SID_ENTERCHAT() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Sent SID_ENTERCHAT") << endl;

            if(! s_pkt_SID_JOINCHANNEL(home_channel)) {
                *Debug << string("ERROR:  s_pkt_SID_JOINCHANNEL(home_channel)")
                         + string(" broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")")
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Sent SID_JOINCHANNEL") << endl;
        } break;
    


        case SID_ENTERCHAT: {
            if(! r_pkt_SID_ENTERCHAT()) {
                *Debug << (string("ERROR:  r_pkt_SID_ENTERCHAT() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Recv SID_ENTERCHAT") << endl;
            login_complete = true;
        } break;


        case SID_NULL: {
            // do nothing.
            *Debug << string("Recv SID_NULL") << endl;
        } break;
    
        case 0x4A: {
            // Just trash the packet and do nothing.  This is just a
            // request for user stats and comp info.  I don't (I
            // think?) need to send anything back and can ignore it.

            // *debug << "DEBUG:  got a 0x4A from the server (nothing bad)." 
            //        << endl
            //        << "          " << __FILE__ << "(" 
            //        << QString::number(int(__LINE__)).ascii() << ")" << endl;
            if(! trash_curr_packet()) {
                *Debug << (string("ERROR:  Problem trashing packet 0x4A.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"))
                       << endl;
                conlog << string("Connect attempt failed.") << endl;
                disconnectNow();
                return false;
            }
            *Debug << string("Recv 0x4A") << endl;
        } break;

        default:
            // shit! what do we do?  stuff's comin all weird, wrong or
            // out of order.
            *Debug << (string("ERROR:  Unexpected packet: ")
                     + string("          ") + string( __FILE__ )
                     + string("(") 
                     + string( QString::number(int(__LINE__)).ascii() ) 
                     + string(")"))
                   << endl;
            *Debug << (recvBuff.print_buffer()) << endl;
            conlog << string("Connect attempt failed.") << endl;
            disconnectNow();
            return false;
        }  // end of switch(recvBuff.getPacketID())

        recvBuff.setStripped( false );
    } // end of while(!login_complete)

    if( !qtsocket.isValid() )
    {
        conlog << string("Connect attempt failed.") << endl;
        disconnectNow();
        return false;
    }

    conlog << "Server logon successful.   :)" << endl;
    startTime = QDateTime::currentDateTime();
    *Debug << "DEBUG:  Got through the readconfig and connectToBnet! Woot!"
           << endl;

    emit connectedToBnet();

    conlog << "DEBUG:  emitted connectedToBnet()" << endl;

    // OMG, it completed!?
    return true;
}  // end of bool conGuy::connectToBnet()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::sendBuffOut()
//
// Description:
//   This function not only sends the sendBuff to bnet, but it also 
//   manages the delay required to avoid flooding off bnet and getting 
//   IP banned.
//
bool conGuy::sendBuffOut() {
  void* ourData = sendBuff.getPtr();
  int rc;

  // Flood protection: 
  // Do I need to sleep required time on a ping return?  The normal answer
  //   would be that I shouldn't need to.  (It would make the ping time
  //   perceived by bnet to be incorrect.  (6r)).  Here I am fearful
  //   that because the server sends us a lot of pings, that our return
  //   pings may cause a weird 'flood' error on their end.  And since it's
  //   not chat sends/coommands that it floods on, it may exit differently.
  //   I suppose that for now, just to be safe, I will sleep the required
  //   time.  As for how I will handle the SID_PING returns, check the
  //   code for that one.  If you don't know what(where) I'm talking about,
  //   it's in the gimmeEvent() function, and inside that function, the
  //   call to handle_nonchat().  

  //  if( (sendBuff.getPacketID() != SID_PING) && (!sleepRequiredTime()) ) {
  if( !sleepRequiredTime()) {
    *Debug << "ERROR: sleepRequiredTime() failed on a sendBuffOut()" << endl;
    conlog << "ERROR:  Problems encountered with flood control." << endl;
    return false;
  }
  rc = writeit(ourData, sendBuff.size());
  if(rc < 0) {
    *Debug << "ERROR:  Can't send data.  (sendBuffOut())" << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!updateLastSendTime()) {
    // Should this be an error or a warning?
    *Debug << "ERROR:  updateLastSendTime() failed in sendBuffOut()" << endl;
    conlog << "ERROR:  Problems encountered with flood control." << endl;
    // Should I return false or true?  Technically it DID send... 
    // Eh, chances are that the update time function fails is too low to care.
    return false;
  }
  return true;
}  // end of bool conGuy::sendBuffOut()



//////////////////////////////////////////////////////////////////////////////
// 
// Function:  bool conGuy::s_pkt_protocol()
//
// Description:
//   Send protocol byte.  (game protocol)
//
bool conGuy::s_pkt_protocol() {
  u8 protocol_byte = 0x01;  // game protocol == 0x01.
  int rc;
  rc = writeit(&protocol_byte, 1 /* byte */);
  if(rc < 0) {
    *Debug << "ERROR:  Can't send data for protocol byte..  " << endl;      
    return false;
  }
  this->msleep(70);  // Protection against more than 1 send merging
		     // with another.
  // It's also to avoid flooding and getting IP banned.  
  return true;
}  // end of bool conGuy::s_pkt_protocol()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_AUTH_INFO()
//
// Description:
//   Send SID_AUTH_INFO packet.  This is just basic stuff about the computer
//   running the program.
//
bool conGuy::s_pkt_SID_AUTH_INFO() {
  u8 packetID = (u8)SID_AUTH_INFO;
  u32 protocolID = 0;
  char platformID[5]  = "68XI";
  char programID[5]   = "PXES";
  u32 versionByte     = (u32)0xcd; // (u32)0xc9 for version 1.11, 1.11b, 
                                   // (u32)0xcb for 1.12
                                   // (u32)0xcd for 1.13, 1.13b, 1.13c
  u32 productLanguage = (u32)0x00;    // unknown.  always 0.
  u32 localIP         = (u32)0x00;    // It should log on ok...
  u32 timeBias        = (u32)0x012c;  // EST.  East side baby.
  u32 localeID        = (u32)0x0409;  // english
  u32 languageID      = (u32)0x0409;  // english
  char countryAbbrev[4]  = "USA";
  char country[14]       = "United States";

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&protocolID,      sizeof(u32));
  sendBuff.add(platformID, 4);
  sendBuff.add(programID,  4);
  sendBuff.add(&versionByte,     sizeof(u32));
  sendBuff.add(&productLanguage, sizeof(u32));
  sendBuff.add(&localIP,         sizeof(u32));
  sendBuff.add(&timeBias,        sizeof(u32));
  sendBuff.add(&localeID,        sizeof(u32));
  sendBuff.add(&languageID,      sizeof(u32));
  sendBuff.add(countryAbbrev,    strlen(countryAbbrev)+1);
  sendBuff.add(country,          strlen(country)+1);
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_AUTH_INFO failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_AUTH_INFO failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();
  return true;
}  // end of bool conGuy::s_pkt_SID_AUTH_INFO()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_PING(u32 &pingValue)
//
// Description:
//   Receive a ping value.
//
bool conGuy::r_pkt_SID_PING(u32 &pingValue) {
  // Return the successfulness of extraction.
  return recvBuff.extract(pingValue);
}


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_PING(u32 pingValue)
//
// Description: 
//   This function sends back the ping we got from r_pkt_SID_PING().
//   
// Note:
//   I don't think I send back the ping anymore.  I had some glitchy problems
//   with this.  If you have time, look this problem over again.  (ping return
//   is not essential).
// 
bool conGuy::s_pkt_SID_PING(u32 pingValue) {
  // Send it back.
  sendBuff.clear();
  sendBuff.setPacketID(SID_PING);
  if(! sendBuff.add(&pingValue, sizeof(u32))) {
    *Debug << "ERROR:  in s_pkt_SID_PING(), couldn't add pingValue to sendBuff"
	   << endl << "             " << __FILE__ << "(" << __LINE__ << ")"
	   << endl;
    return false;
  }
  if(! sendBuff.package()) {
    *Debug << "ERROR:  in returnPing(), couldn't package sendBuff"
	   << endl << "             " << __FILE__ << "(" << __LINE__ << ")"
	   << endl;
    return false;
  }
  return sendBuffOut();  
}  // end of bool conGuy::s_pkt_SID_PING(u32 pingValue)



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_AUTH_INFO()
//
// Description:
//   This packet gives us variables and the equation used to do the 
//   game version check.
//
bool conGuy::r_pkt_SID_AUTH_INFO() {
  // This is the format of the packet.
  
  //     (DWORD)		 Logon Type
  //     (DWORD)		 Server Token
  //     (DWORD)		 UDPValue**
  //     (FILETIME)	 MPQ filetime
  //     (STRING)		 IX86ver filename
  //     (STRING)		 ValueString


  u32 logonType;     // Logon Type.  (0 == Broken Sha-1 (STAR/SEXP/D2DV/D2XP)
                     //               1 == NLS version 1 (War3Beta),
                     //               2 == NLS Version 2 (WAR3))
  u32 serverToken;

  u32 udpValue;       // We won't use this.  See note below.

  // The next two variables are actually the two ints from a timeval.
  // This is extracted from the packet, but we don't use it in our
  // login process.
  u32 mpqFiletimeVal1;// We won't use this extracted value
  u32 mpqFiletimeVal2;// We won't use this extracted value.

  char hash_mpq[64];   // We extract the mpq ver from this.
  char hash_equ[128];   // This contains the equation.
  
  // Note about udpValue:
  //   No one really knows what this is, however, it is used in 2nd DWORD of 
  //   the UDP packet PKT_CONNTEST2 (0x09). It has also been seen in the realm 
  //   logon blob.  PKT_CONNTEST2 is used to establish that the client supports
  //   UDP.

  // Extract the packet stuff's.
  recvBuff.extract(logonType);
  recvBuff.extract(serverToken);
  recvBuff.extract(udpValue);
  recvBuff.extract(mpqFiletimeVal1);
  recvBuff.extract(mpqFiletimeVal2);
  recvBuff.extract(hash_mpq);
  recvBuff.extract(hash_equ);
  
  // Set the values in the authGuy object.
  auth.setLogonType(logonType);
  auth.setServerToken(serverToken);
  auth.setUdpValue(udpValue);
  auth.setHash_mpq(hash_mpq);
  auth.setHash_equ(hash_equ);
  

  return true;
}  // end of bool conGuy::r_pkt_SID_AUTH_INFO()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_AUTH_CHECK()
//
// Description:
//   This packet sends out the info concerning the game version checking.
//
bool conGuy::s_pkt_SID_AUTH_CHECK() {
  u32 i;
  u32 cdkeyValue2;
  u8 packetID = SID_AUTH_CHECK;

  // Below are the elements of this packet in this order.
  u32 clientToken;
  u32 exeVer;
  u32 checkRevisionResult;
  u32 numKeys;
  u32 spawn;
  u32 keyLength;
  u32 cdkeyValue0;  // (product)
  u32 cdkeyValue1;
  u32 zero_u32 = 0x00;  // unknown field.
  u32 *hashed_data;  // This points to 5 u32's.  (20 bytes).
  string exe_info;
  string owner_name;

  // Store stor'in thingS: 
  clientToken = auth.getClientToken();
  // TODO:  Learn how to get the EXE Version (u32) in here sometime.
  //     (hardcoded for now.  FIX!)
  //   exeVer = 0x01010101;   // for v 1.11
  //   exeVer = 0x01010103;   //  for v 1.11b
  //   exeVer = 0x00000001;   // for v 1.12  (but then it stopped working...)
  //   exeVer = 0x01010203;   // for v 1.12
  //   exeVer = 0x01010301;     // for v 1.13
  //   exeVer = 0x01010303;     // for v 1.13b
  //   exeVer = 0x01010305;     // for v 1.13c
  //   exeVer = 0x01010307;   // for v 1.13d
  //   exeVer = 0x01010309;   // for v 1.13e
  exeVer = 0x0101030b;   // for v 1.13f

  checkRevisionResult = auth.checkRevision();
  numKeys = 0x01;  // only 1 key for sc/bw.
  spawn   = 0x00;  // no spawn
  keyLength = 0x0d;  // sc/bw has key length of 13
  
  if(!auth.getCDVars()) {
    *Debug << "ERROR:   getCDVars failed." << endl
	   << "         " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  cdkeyValue0 = auth.getCdkeyVar0();
  cdkeyValue1 = auth.getCdkeyVar1();
  cdkeyValue2 = auth.getCdkeyVar2();
  
  
  // DEBUG output:
  *Debug << "DEBUG:  CDkey values:" << endl;
  for(i = 0; i < sizeof(u32); i++) {
    *Debug << print_hex_form( *(((u8*)&cdkeyValue0) +i) );
  }
  *Debug << endl;  
  for(i = 0; i < sizeof(u32); i++) {
    *Debug << print_hex_form( *(((u8*)&cdkeyValue1) +i) );
  }
  *Debug << endl;  
  for(i = 0; i < sizeof(u32); i++) {
    *Debug << print_hex_form( *(((u8*)&cdkeyValue2) +i) );
  }
  *Debug << endl;  

  hashed_data = auth.getHashData();

  exe_info = auth.getExeInfo();
  owner_name = "GiTMbot user";

  // done setting the vars we need, now prepare the packet.
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&clientToken,         sizeof(u32));
  sendBuff.add(&exeVer,              sizeof(u32));
  sendBuff.add(&checkRevisionResult, sizeof(u32));
  sendBuff.add(&numKeys,             sizeof(u32));
  sendBuff.add(&spawn,               sizeof(u32));
  sendBuff.add(&keyLength,           sizeof(u32));
  sendBuff.add(&cdkeyValue0,         sizeof(u32));
  sendBuff.add(&cdkeyValue1,         sizeof(u32));
  sendBuff.add(&zero_u32,            sizeof(u32));
  for(i = 0; i < 5; i++)
  {
    sendBuff.add(&hashed_data[i],    sizeof(u32));
  }
  sendBuff.add(exe_info.c_str(),     exe_info.length()+1);
  sendBuff.add(owner_name.c_str(),   owner_name.length()+1);


  // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_AUTH_CHECK failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_AUTH_CHECK failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }

  //   *Debug << "DEBUG:  Here is the sent SID_AUTH_CHECK packet:" << endl;
  //   sendBuff.print_buffer();
  sendBuff.clear();
  

  return true;

} // end of bool conGuy::s_pkt_SID_AUTH_CHECK()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_AUTH_CHECK()
//
// Description:
//   This packet gives us the result of our game version check send.
//
bool conGuy::r_pkt_SID_AUTH_CHECK() {
  u32 result;  // result of the auth
  string additionalInfo;  // Additional info msg.
  
  recvBuff.extract(result);
  recvBuff.extract(additionalInfo);

  switch (result) {
  case 0x000:
    // Auth challenge passed.
    return true;
    break;
  case 0x100:
    *Debug << "ERROR:  Old Game Version.  Patch MPQ file: "
	   << additionalInfo << endl;
    conlog << "ERROR:  Old Game Version.  Patch MPQ file: "
	   << additionalInfo << endl;
    return false;
    break;
  case 0x101:
    *Debug << "ERROR:  Invalid Game Version.  " << endl;
    conlog << "ERROR:  Invalid Game Version.  " << endl;
    return false;
  case 0x200:
    *Debug << "ERROR:  Invalid CD key. " << endl;
    conlog << "ERROR:  Invalid CD key. " << endl;
    return false;
    break;
  case 0x201:
    *Debug << "ERROR:  CD key is in use!  OMFG.  Kill this guy: " 
	   << additionalInfo << endl;
    conlog << "ERROR:  CD key is in use!  OMFG.  Kill this guy: " 
	   << additionalInfo << endl;
    return false;
    break;
  case 0x202:
    *Debug << "ERROR:  Aww shitters.  You're using a banned cd key." << endl;
    conlog << "ERROR:  Aww shitters.  You're using a banned cd key." << endl;
    return false;
    break;
  case 0x203:
    *Debug << "ERROR:  Wrong Product." << endl;
    conlog << "ERROR:  Wrong Product." << endl;
    return false;
    break;
  default:
    *Debug << "ERROR:  Unknown response for SID_AUTH_CHECK (0x51)." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    conlog << "ERROR:  Unknown response for SID_AUTH_CHECK (0x51)." << endl;
    return false;
    break;
  }
  return false;
}  // end of bool conGuy::r_pkt_SID_AUTH_CHECK()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_LOGONRESPONSE()
//
// Description: 
//   Send out the logon info.  This includes the hash of the password and
//   client and server tokens.  The account name is also sent as a string.
//
bool conGuy::s_pkt_SID_LOGONRESPONSE() {
  u32 packetID = SID_LOGONRESPONSE;
  u32 clientToken = auth.getClientToken();
  u32 serverToken = auth.getServerToken();
  u32 password_hash[5];
  string account = auth.getAccount();


  // copy the password_hash over from the authGuy object.
  memcpy(password_hash, auth.getPasswordHash(), sizeof(u32) * 5);

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&clientToken,         sizeof(u32));
  sendBuff.add(&serverToken,         sizeof(u32));
  sendBuff.add(password_hash,        sizeof(u32) * 5);
  sendBuff.add(account.c_str(),      account.length() + 1);
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_LOGONRESPONSE failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_LOGONRESPONSE failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();
  return true;
} // end of bool conGuy::s_pkt_SID_LOGONRESPONSE()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_LOGONRESPONSE() 
//
// Description:
//   This packet gives us the status response to our password hash send.
//
bool conGuy::r_pkt_SID_LOGONRESPONSE() {
  // result of the password send.
  u32 result;
  recvBuff.extract(result);
  
  if(result == 0x01) {
    // login success.
    *Debug << "DEBUG:  Logon success" << endl;
    conlog << "Account and password logon success." << endl;
    return true;
  }
  else if(result == 0x00) {
    // invalid password.
    *Debug << "ERROR:  Invalid password" << endl;
    conlog << "ERROR:  Invalid password" << endl;
    return false;
  }
  else {
    // uhh.... dunno what this. 
    *Debug << "ERROR:  Unknown SID_LOGONRESPONSE from server.  Got result == ";
    conlog << "ERROR:  Unknown SID_LOGONRESPONSE from server.  Got result == ";
    for(u32 i = 0; i < sizeof(u32); i++) {
      *Debug << print_hex_form( *(((u8*)&result) +i) );
      conlog << print_hex_form( *(((u8*)&result) +i) );
    }
    return false;
  }
} // end of bool conGuy::r_pkt_SID_LOGONRESPONSE()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_UDPPINGRESPONSE()
//
// Description:
//   This packet lets bnet know we can receive UDP packets.  We won't handle
//   UDP packets though.  I'm sending this because it's part of the logon
//   sequence and we can use it to set our ping to essentially 0.
// 
// Note: 
//   I'm not sure if that statement above is accurate.  I have shitty memory
//   and can't remember what I actually chose to do when I coded it.
//   Probably another good reason to comment as I code things...
//
bool conGuy::s_pkt_SID_UDPPINGRESPONSE() {
  u8 packetID = SID_UDPPINGRESPONSE;
  u32 udpCode;  // This is the value given to us via UDP packet 
                // PKT_SERVERPING (0x05).

                // For 0 ping, send "tenb".

  // Memcpy the DWORD(u32) of the string "bnet", which is "tenb".
  memcpy(&udpCode, "tenb", strlen("tenb"));    // (without the null)

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&udpCode,      sizeof(u32));
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_UDPPINGREPONSE failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_UDPPINGRESPONSE failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();  
  return true;
}  // end of bool conGuy::s_pkt_SID_UDPPINGRESPONSE()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_ENTERCHAT()
//
// Description:
//   This function requests a unique username for the account so we can 
//   "enter" chat.
//
bool conGuy::s_pkt_SID_ENTERCHAT() {
  u8 packetID = SID_ENTERCHAT;
  string accountName = auth.getAccount();
  string statString = "";  // Just NULL for cdkey-ed products.
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(accountName.c_str(),      accountName.length()+1);
  sendBuff.add(statString.c_str(),       statString.length() +1);
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_ENTERCHAT failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_ENTERCHAT failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();  
  return true;
} // end of bool conGuy::s_pkt_SID_ENTERCHAT()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_ENTERCHAT() 
//
// Description:
//   Receive the enterchat packet.  We get a unique username here.  
//   (which could be just the account name or the account name 
//   with #2, #3 etc.  appended to the end.
//
bool conGuy::r_pkt_SID_ENTERCHAT() {
//   *Debug << "DEBUG:  data in r_pkt_SID_ENTERCHAT: " << endl << endl;
//   recvBuff.print_buffer();
//   *Debug << endl;

  string statstring;  // We'll not save this anywhere.
  
  recvBuff.extract(unique_username);
  recvBuff.extract(statstring);    // This is the 'record' games of the player
  recvBuff.extract(account);
  
  return true;
  
} // end of bool conGuy::r_pkt_SID_ENTERCHAT()



//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_GETCHANNELLIST()
//
// Description:
//   Request for the available public channels
//
// Notes:
//   Getting the channel list has no purpose for the bot.  At one point
//   I thought it was necessary in the login procedure.  Apparently not! :)
// 
bool conGuy::s_pkt_SID_GETCHANNELLIST() {
  u8 packetID   = SID_GETCHANNELLIST;
  u32 productID = BROOD_WAR_PRODUCT_ID;    // #define in conGuy.h
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&productID, sizeof(u32));
  

//   *Debug << "DEBUG:  This is what our pkt send SID_GETCHANNELLIST looks like:"
// 	 << endl << endl;
//   sendBuff.print_buffer();  // Debug
//   *Debug << endl;


  // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_GETCHANNELLIST failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_GETCHANNELLIST failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();    
  
  
  return true;
}  // end of bool conGuy::s_pkt_SID_GETCHANNELLIST()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_GETCHANNELLIST()
//
// Description: 
//   Receive a whole bunch of channel names that are available.
//
// Other notes:
//   This packet has a list of null-terminated strings
//   (actually they are UTF-8, but I parse them as ascii)
//   which are terminated by a empty string.
//   I'm just throwing them away.  
//
bool conGuy::r_pkt_SID_GETCHANNELLIST() {
//   *Debug << "DEBUG:  This is what our r_pkt_SID_GETCHANNELLIST looks like:"
// 	 << endl << endl;
//   recvBuff.print_buffer();
//   *Debug << endl;
  
  string aChannel;
  recvBuff.extract(aChannel);
  while(aChannel.length() != 0) {
    recvBuff.extract(aChannel);
  }
  
  return true;
} // end of bool conGuy::r_pkt_SID_GETCHANNELLIST()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_CHATCOMMAND(string msg)
//
// Description:  
//   Send some text just like you would in the actual brood war game.
//   This includes /join 's and such.
//
bool conGuy::s_pkt_SID_CHATCOMMAND(string msg) {
  u8 packetID = SID_CHATCOMMAND;
  string command = msg;

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  

  sendBuff.add(command.c_str(), command.length()+1);

  // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_CHATCOMMAND failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_CHATCOMMAND failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();    

  return true;
} // end of bool conGuy::s_pkt_SID_CHATCOMMAND(string msg)


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_JOINCHANNEL(string channel)
//
// Description:
//   I use this only in the beginning to join a channel opon login.
//   I use this because I suppose it's a request to join a channel before
//   we have officially "entered chat".  After we've offically "entered chat"
//   I join channels by just plain sending the "/join <channel>" as a 
//   chat command/message.
//
// Other notes:
//
//   Join types (flags value):						
//     0x00 - Standard request to join; bnet will notify you if the channel
//       is full or restricted.						
//     0x01 - Join product-specific channel. W3 clients on accounts in a 
//       clan will end up in their Clan channel.       
//     0x02 - Create channel. Bnet will ipban if the channel is restricted,  
//       but this can be used to join full channels.			 
//
//   From my packet logs, it seems as if 0x02 joins existing channels as well.
//   Dunno... I'll just always send 0x02 as my flag.  We can fix this
//   later as it works as just the initial log in send.
//
bool conGuy::s_pkt_SID_JOINCHANNEL(string channel) {

  u8 packetID = SID_JOINCHANNEL;
  u32 flags = 0x02;
  string toJoin = channel;


  sendBuff.clear();
  sendBuff.setPacketID(packetID);

  sendBuff.add(&flags, sizeof(u32));
  sendBuff.add(toJoin.c_str(), toJoin.length()+1);
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_JOINCHANNEL failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_JOINCHANNEL failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();    
  return true;
}  // end of bool conGuy::s_pkt_SID_JOINCHANNEL(string channel)


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_NULL()
//
// Description:
//   This is a blank packet just to help keep the connection alive.
//
bool conGuy::s_pkt_SID_NULL() {
  u8 packetID = SID_NULL;
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
    // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_NULL failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_NULL failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();    
  return true;
}  // end of bool conGuy::s_pkt_SID_NULL()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::s_pkt_SID_LEAVECHAT()
//
// Description:
//   This is sent right before disconnecting.  Leaves 'chat'.
//
// Notes:
//   I have currently taken this packet out of the logout sequence.  (See the
//   disconnectNow() function.)  The reason is because sometimes
//   I would get some kind of error of some sort, or somehow get into some
//   state in which I would need to disconnect.  The disconnect function
//   would call this s_pkt_SID_LEAVECHAT() function and try to send another
//   packet to an already screwed up socket descriptor.  And so... the next 
//   time I start up the bot, the OS gives me some weird error(sorry, I didn't
//   write down what it was.)  about the socket.  Not sending this 
//   packet keeps me from getting that weird error.  We can see what it is
//   again if we take the chat client version of GiTMbot and uncomment
//   the call to this function in disconnectNow().  You'll see it eventually.
//   
//   TODO:  A solution that may work and might be implemented one day is to
//   do a quickly-returning poll on the socket and then check if the 
//   error returned is something that tells us the socket has been closed.
//
bool conGuy::s_pkt_SID_LEAVECHAT() {
  u8 packetID = SID_LEAVECHAT;
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
    // Package and send the packet off.
  if(!sendBuff.package()) {
    *Debug << "ERROR:  packaging for packet SID_LEAVECHAT failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  if(!sendBuffOut()) {
    *Debug << "ERROR:  send for packet SID_LEAVECHAT failed." << endl
	   << "        " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  sendBuff.clear();    
  return true;
}  // end of bool conGuy::s_pkt_SID_LEAVECHAT()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_FLOODDETECTED()
//
// Description:
//   This function is here to make it consistent with the way I'm 
//   doing everything already.
//
bool conGuy::r_pkt_SID_FLOODDETECTED() {
  // This packet has no data.
  return true;
}

//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::r_pkt_SID_MESSAGEBOX()
//
// Description:
//   This function receives the data that would be received if there was a
//   message box that needed to be displayed.  The data in this packet
//   would normally be able to be passed directly into a MS VC++ or MS VB
//   messagebox() function.  Since I'm using g++/gcc, I'll just log
//   the contents of the data here.
//   
bool conGuy::r_pkt_SID_MESSAGEBOX() {
  u32 style;
  string caption;
  string text;
  
  recvBuff.extract(style);
  recvBuff.extract(caption);
  recvBuff.extract(text);

  *Debug   << "DEBUG: msgbox:   " << caption << endl << endl
	   << "                 " << text    << endl << endl;
  conlog << "MessageBox:    " << caption << endl << endl
	   << "               " << text    << endl << endl;
  return true;
}  //  end of    bool conGuy::r_pkt_SID_MESSAGEBOX()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  event conGuy::gimmeEvent()
//
// Description:
//   Returns an event object of the next event that happened in the 
//   current channel that the conGuy is in.
//
// Fixes:
//
// 8/14/2004
// As a fix to this s_pkt_SID_NULL, I'm going to instead just say
// something in the channel.
//
Event conGuy::gimmeEvent() {
  Event newEvent;   // <-- return value.

  // If the !exit command was used, then just return a useless newEvent.
  // If the conGuy is not connected, just return a useless newEvent.
  if(done == true || isConnected() == false) {
      newEvent.try_again = true;
      return newEvent;
  }

  int timeout = 50;
  if( !readFullPacket( timeout ) )
  {
      // Timeout occurred.  Nothing to read in.  (Hopefully it wasn't
      // an error).

      // Aquire a lock on the mutex for our sendQueue, then send
      //   an item in it if it has anything in it.
      //      int rc;
      toSend.mutex.lock();
      if(toSend.out.size() > 0) {
          // Goodie.  We have some treats for us!
          string msg = toSend.out.front();
          toSend.out.pop_front();

          if(! say(msg)) {
              *Debug << "ERROR:  send for packet SID_CHATCOMMAND failed."
                  << endl;
              conlog << "ERROR:  Send for packet SID_CHATCOMMAND failed."
                  << endl;
              toSend.mutex.unlock();
              newEvent.exit_now = true;
              return newEvent;
          }
      }
      toSend.mutex.unlock();


      // Acquire a lock on our mutex for our sendRequests requests.
      //   Send stuff it has stuff needing to be sent.
      requests.mutex.lock();
      if(requests.out.size() > 0) {
          // Goodie, we have some conGuys that would like our services.
          string msg         = requests.out.front();
          conGuy *comingFrom = requests.fromConGuy.front();
          string fromUser    = requests.fromWho.front();
          requests.out.pop_front();
          requests.fromConGuy.pop_front();
          requests.fromWho.pop_front();
          // Send it
          if(! say(msg)) {
              *Debug << "ERROR:  send for packet SID_CHATCOMMAND failed."
                  << endl;
              conlog << "ERROR:  Send for packet SID_CHATCOMMAND failed."
                  << endl;
              requests.mutex.unlock();
              newEvent.exit_now = true;
              return newEvent;
          }
          else {
              // Push onto 'awaiting' lists.
              requests.waiting.push_back(msg);
              requests.returnToConGuy.push_back(comingFrom);
              requests.returnToWho.push_back(fromUser);
              *Debug << "just push_back-ed onto waiting list.  "
                  << "waiting.size() == " << requests.waiting.size() 
                  << endl;
          }
      }
      requests.mutex.unlock();


      // Ask the calling object to try to get an event again because
      //   this event has no useful infomation in it.
      newEvent.exit_now = false;
      newEvent.try_again = true;
      return newEvent;
  }
  else
  {
      // We read something into our buffer.

      // Let's analyze this packet.
      //   char pktID[16];
      //   sprintf(pktID, "%d", recvBuff.getPacketID());
      //   *Debug << (string("Processing input packet ") + string(pktID)) 
      //          << endl;

      // The 'event' switch.
      switch(recvBuff.getPacketID()) {
      case SID_CHATEVENT:
    
          *Debug << "DEBUG:  just got SID_CHATEVENT.  ";
          //     *Debug << "This buffer's data:.." << endl;
          //     recvBuff.print_buffer();
          //     *Debug << "DEBUG:  now extracting..." << endl;
          // Format:
          // (u32) Event ID
          // (u32) Flags
          // (u32) Ping
          // (u32) IP Address*
          // (u32) Account number*
          // (u32) Registration authority*
          // (STRING) Username
          // (STRING) Text
    
          // * == defunct.
          u32 defunct_value;  // This will get thrown away.

          recvBuff.extract(newEvent.eventID);
          recvBuff.extract(newEvent.flags);
          recvBuff.extract(newEvent.ping);
          recvBuff.extract(defunct_value);  
          recvBuff.extract(defunct_value);
          recvBuff.extract(defunct_value);
          recvBuff.extract(newEvent.username);
          recvBuff.extract(newEvent.text);
    
          *Debug << "DEBUG:  finished doin extracts." << endl;
          break;
      default:
          // Handle a non-chatevent.
          *Debug << "DEBUG:  Handling nonchat packet..." << endl;
          if(! handle_nonchat()) {
              disconnectNow();
              newEvent.exit_now = true;
          }
          else {
              // Try to get another event...
              newEvent.exit_now = false;
              newEvent.try_again = true;
          }
          break;
      } // End of switch().
      recvBuff.setStripped( false );
      return newEvent;
  }

} // end of    event conGuy::gimmeEvent()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::handle_nonchat()
// 
// Description: 
//   This function handles a packet that is recieved that is not part of
//   the standard chat messages that we want to parse.
// 
//   These are the packet SID's:
//     SID_FLOODDETECTED
//     SID_MESSAGEBOX
//     SID_PING
//     SID_FRIENDLIST
//     SID_FRIENDUPDATE
//     SID_FRIENDADDED
//     SID_FRIENDREMOVED
//     SID_FRIENDMOVED
//     SID_NULL
//
// 
bool conGuy::handle_nonchat() {

  switch(recvBuff.getPacketID()) {


  case SID_FLOODDETECTED:
    // Server will disconnect us right after sending this message.
    conlog << "Flood detected from server." << endl;

    // r_pkt_SID_FLOODDETECTED() does absolutely nothing!!
    //   It's just there for consistency.
    if(! r_pkt_SID_FLOODDETECTED()) {
      *Debug << "ERROR:  r_pkt_SID_FLOODDETECTED() failed." << endl;
      conlog << "ERROR:  r_pkt_SID_FLOODDETECTED() failed." << endl;
      return false;
    }
    *Debug << "DEBUG:  just got SID_FLOODDETECTED." << endl;
    // false means quit in this case.
    return false;
    break;


  case SID_MESSAGEBOX:

    if(! r_pkt_SID_MESSAGEBOX()) {
      *Debug << "ERROR:  r_pkt_SID_MESSAGEBOX() failed." << endl;
      conlog << "ERROR:  r_pkt_SID_MESSAGEBOX() failed." << endl;
      return false;
    }
    *Debug << "DEBUG:  just got SID_MESSAGEBOX." << endl;
    return true;
    break;


  case SID_PING: {
    u32 pingVal;

    // For Debug timestamp.
    time_t rawtime;
    struct tm * timeinfo;

    if(! r_pkt_SID_PING(pingVal)) {
      *Debug << "ERROR:  r_pkt_SID_PING() failed." << endl;
      conlog << "ERROR:  r_pkt_SID_PING() failed." << endl;
      return false;
    }
    // Take a timestamp for the Debug output.
    time ( &rawtime );
    timeinfo = localtime ( &rawtime );
    *Debug << "DEBUG:  just got SID_PING.  "
	   << string(asctime(timeinfo)) << endl;

    if(! s_pkt_SID_PING(pingVal)) {
      *Debug << "ERROR:  s_pkt_SID_PING() failed." << endl;
      conlog << "ERROR:  s_pkt_SID_PING() failed." << endl;
      return false;
    }
    // Take a timestamp for the Debug output.
    time ( &rawtime );
    timeinfo = localtime ( &rawtime );
    *Debug << "DEBUG:  just sent SID_PING.  " 
	   << string(asctime(timeinfo)) << endl;
    
    return true;
  } break;

    // TODO:  These should probably be put in the event switch.
    // Search for "The 'event' switch." to see which one I mean.
  case SID_FRIENDLIST:
  case SID_FRIENDUPDATE:
  case SID_FRIENDADDED:
  case SID_FRIENDREMOVED:
  case SID_FRIENDMOVED:


  case SID_NULL:
  default:
    return trash_curr_packet();
  }  // End of switch(recvBuff.getPacketID())
} // end of   bool conGuy::handle_nonchat()


//////////////////////////////////////////////////////////////////////////////
//
// Function:  bool conGuy::trash_curr_packet() 
//
// Description:
//   This is a packet we don't care about.  Trash the current stripped 
//   packet in the buffer.  NOT clear the buffer!
//
bool conGuy::trash_curr_packet() {
  
  u16 size = recvBuff.getPacketSz();

  *Debug << "DEBUG:  About to trash pkt "
	 << print_hex_form(recvBuff.getPacketID()) << ".";
//   *Debug << "Here it is before "
// 	 << "stripping and trashing:" << endl;
  
//   *Debug << "0xFF "                                   // Initial byte
// 	 << print_hex_form(recvBuff.getPacketID())    // Packet ID
// 	 << print_hex_form( * (((u8*)&size)+0) )  // Size (1st byte)
// 	 << print_hex_form( * (((u8*)&size)+1) ); // Size (2nd byte)
  for(int i = 0; i < size; i++) {
    u8 currByte;
    recvBuff.extract(currByte);
//     *Debug << print_hex_form( currByte );
  }
//   *Debug << endl;

  *Debug << "DEBUG:  Finished trashing packet " 
	 << print_hex_form(recvBuff.getPacketID()) << endl;
  if(recvBuff.getPacketSz() != 0) {
    *Debug << "DEBUG:  CRAPpo!  This packet shoulda got trashed, "
	   << "but packetSz isn't 0." << endl
	   << "          " << __FILE__ << "(" << __LINE__ << ")" << endl;
    return false;
  }
  return true;
}  // end of   bool conGuy::trash_curr_packet() 


string conGuy::printErrorType( QSocketDevice::Error err )
{
    switch( err )
    {
    case QSocketDevice::NoError:
        return "QSocketDevice::NoError";
    case QSocketDevice::AlreadyBound:
        return "QSocketDevice::AlreadyBound";
    case QSocketDevice::Inaccessible:
        return "QSocketDevice::Inaccessible";
    case QSocketDevice::NoResources:
        return "QSocketDevice::NoResources";
    case QSocketDevice::InternalError:
        return "QSocketDevice::InternalError";
    case QSocketDevice::Impossible:
        return "QSocketDevice::Impossible";
    case QSocketDevice::NoFiles:
        return "QSocketDevice::NoFiles";
    case QSocketDevice::ConnectionRefused:
        return "QSocketDevice::ConnectionRefused";
    case QSocketDevice::NetworkFailure:
        return "QSocketDevice::NetworkFailure";
    case QSocketDevice::UnknownError:
        return "QSocketDevice::UnknownError";
    default:
        return "Unknown QSocketDevice Error";
    }
}


// precondition: recvBuff has either already stripped the header bytes
// from a previous recv or it has not.  Most of the time, it will
// still need to strip the header bytes.  If it needs to strip the
// header bytes, then it goes into the first section where it tries to
// read enough for the header.  If it doesn't need to strip the
// header, it checks to make sure there are enough bytes in the
// buffer.  If not, it tries to read more.

// postcondition: recvBuff will contain bytes which holds at least a
// full packet.  (possible to have more bytes after that too).  The
// recvBuff will have already stripped the header (recvBuff.stripped()
// == true) and have the the size() and packetSz() set accordingly.

// timeout is in milliseconds.

// NOTE: Anyone who finishes extracting the packet must reset
// recvBuff.stripped() to false.  That way this function knows it
// should try to read another full packet.
bool conGuy::readFullPacket( const int& timeout )
{
    //    *Debug << string("DEBUG:  reading full packet...") << endl;
    int rc;
    unsigned char conGuyBuff[MAX_MSG];
    bool timedOut;
    Q_LONG bytesAvail = 0;

    if( !recvBuff.stripped() )
    {
        if( recvBuff.size() < 4 )
        {
            bytesAvail = qtsocket.waitForMore( timeout, &timedOut );
            if( bytesAvail == -1 )
            {
                // Socket error.
                conlog << ( string("ERROR:  ") + 
                            printErrorType( qtsocket.error() ) ) 
                       << endl;
                disconnectNow();
                return false;
            }
            else if( timedOut == true )
            {
                // Timed out.
                // conlog << string("Connection timed out waiting for data.")
                //        << endl;
                return false;
            }

            // The following is commented out because a readit() will
            // detect the host closing the connection.

            //          else if( bytesAvail == 0 && timedOut == false )
            //          {
            //              conlog << string( "Server closed the connection." )
            //                     << endl;
            //              disconnectNow();
            //              return false;
            //          }



            // Great!  Let's start reading in data now.

            rc = readit(conGuyBuff, MAX_MSG);
            if(rc < 0) {
                *Debug << (string("ERROR:  Can't readit data.  ")
                         + string("          ") + string( __FILE__ )
                         + string("(")
                         + string(QString::number(int(__LINE__)).ascii()) 
                         + string(")"))
                       << endl;
                return false;
            }

            //         char rcStr[16];
            //         sprintf(rcStr, "%d", rc);
            //             *Debug << (string("Got a packet from the server.") +
            //                        string("  rc == ") +
            //                        string(rcStr)) << endl;

            recvBuff.add(conGuyBuff, rc);
        }

        // Strip the header.
        if( !recvBuff.strip() )
        {
            *Debug << string( "Strip failed!." ) << endl;
            disconnectNow();
            return false;
        }
    }
    recvBuff.setStripped( true );
    
    //         *Debug << ( string("got packet id == ") + 
    //                   QString::number(recvBuff.getPacketID()).ascii() )
    //                << endl;
    //         *Debug << ( string("got packet Sz == ") +
    //                   QString::number(recvBuff.getPacketSz()).ascii() )
    //                << endl;
    //         *Debug << ( string("recvBuff size == ") + 
    //                   QString::number(recvBuff.size()).ascii() )
    //                << endl;

    // Check for incomplete packet.
    if( recvBuff.size() < recvBuff.getPacketSz() )
    {
        //         *Debug << string("incomplete packet size") << endl;
        bytesAvail = qtsocket.bytesAvailable();

        // Need to read some more of the packet off the socket.
        Q_LONG toRead = recvBuff.getPacketSz() - recvBuff.size();
        while( bytesAvail < toRead )
        {
            bytesAvail = qtsocket.waitForMore(timeout, &timedOut);
            if( bytesAvail == -1 )
            {
                // Socket error.
                conlog << ( string("ERROR:  waitForMore():  ") + 
                            printErrorType( qtsocket.error() ) )
                       << endl;
                disconnectNow();
                return false;
            }
            else if( timedOut == true )
            {
                // Timed out.
                conlog << string("Connection timed out waiting for the rest ")
                       << string("of the packet data.") 
                       << endl;
                return false;
            }
        }

        // the qsocketdevice says we have enough waiting for what we
        // wanted.  read it.
        rc = readit(conGuyBuff, MAX_MSG);
        if(rc < 0) {
            *Debug << (string("ERROR:  Can't readit data.  ")
                     + string("          ") + string( __FILE__ )
                     + string("(")
                     + string(QString::number(int(__LINE__)).ascii()) 
                     + string(")"))
                   << endl;
            return false;
        }

        //         char rcStr[16];
        //         sprintf(rcStr, "%d", rc);
        //         *Debug << (string("Got a packet from the server. rc == ")
        //                  + string(rcStr))
        //                << endl;

        recvBuff.add(conGuyBuff, rc);
    }

    //    *Debug << string("returning true") << endl;
    return true;
}


// End of conGuy.cc
